VIsualization Workshop

In this course, we only cover the most popular package: ggplot2. Let’s dive into the package!

Lecture Outline

  • Data frame
  • File reading/writing
  • Packages
  • ggplot2

1. Data frame

A data frame is the most common way of storing data in R, and if used systematically makes data analysis easier. Under the hood, a data frame is a list of equal-length vectors. This makes it a 2-dimensional structure, so it shares properties of both the matrix and the list. This means that a data frame has \(\texttt{names()}\), \(\texttt{colnames()}\), and \(\texttt{rownames()}\), although \(\texttt{names()}\) and \(\texttt{colnames()}\) are the same thing. The \(\texttt{length()}\) of a data frame is the length of the underlying list and so is the same as \(\texttt{ncol()}\); \(\texttt{nrow()}\) gives the number of rows.

Creation

You create a data frame using data.frame(), which takes named vectors as input as demonstrated below.

## Demonstration of creating data frames
# Create a data frame
df = data.frame(x = 1:3, y = c("a", "b", "c"))

# Display the structure of the data frame
str(df)
## 'data.frame':    3 obs. of  2 variables:
##  $ x: int  1 2 3
##  $ y: chr  "a" "b" "c"

Beware \(\texttt{data.frame()}\)’s default behaviour which turns strings into

  1. List item
  2. List item

factors. Use \(\texttt{stringsAsFactors = FALSE}\) to suppress this behaviour. Run the demonstration below to see this in action.

## Demonstration of using 'stringAsFactors = FALSE'

# Create a data frame
df = data.frame(
  x = 1:3,
  y = c("a", "b", "c"),
  stringsAsFactors = FALSE
)

# Display the structure of the data frame
str(df)
## 'data.frame':    3 obs. of  2 variables:
##  $ x: int  1 2 3
##  $ y: chr  "a" "b" "c"

Testing and coercion

To check if an object is a data frame, use \(\texttt{class()}\) or test explicitly with \(\texttt{is.data.frame()}\) as is demonstrated below.

## Demonstration of how to check if an object is a data frame

# Check the class of the data frame object
class(df)
## [1] "data.frame"
# Check directly whether the object is a data frame
print(is.data.frame(df))
## [1] TRUE
# NB: do not do this
typeof(df)
## [1] "list"

You can coerce an object to a data frame with \(\texttt{as.data.frame()}\): * A vector will create a one-column data frame. * A list will create one column for each element; it’s an error if they’re not all the same length. * A matrix will create a data frame with the same number of columns and rows as the matrix.

Combining data frames

You can combine data frames using cbind() and rbind() as demonstrated below.

## Demonstration of working with cbind() and rbind()

print(cbind(df, data.frame(z = 3:1)))
##   x y z
## 1 1 a 3
## 2 2 b 2
## 3 3 c 1
print(rbind(df, data.frame(x = 10, y = "z")))
##    x y
## 1  1 a
## 2  2 b
## 3  3 c
## 4 10 z

When combining column-wise, the number of rows must match, but row names are ignored. When combining row-wise, both the number and names of columns must match. Use \(\texttt{plyr::rbind.fill()}\) to combine data frames that do not have the same columns.

It is a common mistake to try and create a data frame by \(\texttt{cbind()}\)ing vectors together. This does not work because \(\texttt{cbind()}\) will create a matrix unless one of the arguments is already a data frame. Instead use \(\texttt{data.frame()}\) directly as demonstrated below.

## Demonstration of common misuse of cbind()

# Do NOT do this
bad = data.frame(
  cbind(a = 1:2, b = c("a", "b"))
)
str(bad)
## 'data.frame':    2 obs. of  2 variables:
##  $ a: chr  "1" "2"
##  $ b: chr  "a" "b"
# Instead do the following
good = data.frame(
  a = 1:2,
  b = c("a", "b"),
  stringsAsFactors = FALSE
)
str(good)
## 'data.frame':    2 obs. of  2 variables:
##  $ a: int  1 2
##  $ b: chr  "a" "b"

The conversion rules for 𝚌𝚋𝚒𝚗𝚍() are complicated and best avoided by ensuring all inputs are of the same type.

Special columns

Since a data frame is a list of vectors, it is possible for a data frame to have a column that is a list:

## Demonstration of working with list columns

# Create a data frame
df = data.frame(x = 1:3)

# Append a list column to the data frame
df$y = list(1:2, 1:3, 1:4)

# Display the result
print(df)
##   x          y
## 1 1       1, 2
## 2 2    1, 2, 3
## 3 3 1, 2, 3, 4

However, when a list is given to \(\texttt{data.frame()}\), it tries to put each item of the list into its own column, so the following code fails:

## Demonstration of a common mistake when combining lists and data frames.
#data.frame(x = 1:3, y = list(1:2, 1:3, 1:4))

A workaround is to use \(\texttt{I()}\), which causes \(\texttt{data.frame()}\) to treat the list as one unit:

## Demonstration of what to do in the situation described above

# Create a data frame
dfl = data.frame(x = 1:3, y = I(list(1:2, 1:3, 1:4)))

# Print the structure of the data frame
str(dfl)
## 'data.frame':    3 obs. of  2 variables:
##  $ x: int  1 2 3
##  $ y:List of 3
##   ..$ : int  1 2
##   ..$ : int  1 2 3
##   ..$ : int  1 2 3 4
##   ..- attr(*, "class")= chr "AsIs"
# Print the value of the element at location (2, "y") in the data frame
print(dfl[2, "y"])
## [[1]]
## [1] 1 2 3

\(\texttt{I()}\) adds the \(\texttt{AsIs}\) class to its input, but this can usually be safely ignored.

Similarly, it is also possible to have a column of a data frame that is a matrix or array, as long as the number of rows matches the data frame. Run the demonstration below to see this in action.

## Extension of the previous example with matrices as values

# Create a data frame
dfm = data.frame(x = 1:3, y = I(matrix(1:9, nrow = 3)))

# Print the structure of the data frame
str(dfm)
## 'data.frame':    3 obs. of  2 variables:
##  $ x: int  1 2 3
##  $ y: 'AsIs' int [1:3, 1:3] 1 2 3 4 5 6 7 8 9
# Print the value of the element at location (2, "y") in the data frame
dfm[2, "y"]
##      [,1] [,2] [,3]
## [1,]    2    5    8

Use list and array columns with caution: many functions that work with data frames assume that all columns are atomic vectors.

2. Data Visualization Using \(\texttt{ggplot2}\)

R has several systems for making graphs, but ggplot2 is one of the most elegant and most versatile. ggplot2 implements the grammar of graphics, a coherent system for describing and building graphs. With ggplot2, you can do more faster by learning one system and applying it in many places.

2.1 Prerequisites

This chapter focusses on ggplot2, one of the core members of the tidyverse. The tidyverse is an opinionated collection of R packages designed for data science. All packages share an underlying design philosophy, grammar, and data structures. To access the datasets, help pages, and functions that we will use in this chapter, install and load the tidyverse by running this code:

# Run this block to install and load the tidyverse package
#install.packages("tidyverse")
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✓ ggplot2 3.3.5     ✓ purrr   0.3.4
## ✓ tibble  3.1.1     ✓ dplyr   1.0.6
## ✓ tidyr   1.1.4     ✓ stringr 1.4.0
## ✓ readr   1.4.0     ✓ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

Line 3 loads the core \(\texttt{tidyverse}\); packages which you will use in almost every data analysis. It also tells you which functions from the tidyverse conflict with functions in base R (or from other packages you might have loaded).

You only need to install a package once, but you need to reload it every time you start a new session.

Throughout this chapter, we will be explicit about where a function (or dataset) comes from and we will use the special form \(\texttt{package::function()}\). For example, \(\texttt{ggplot2::ggplot()}\) tells you explicitly that we are using the \(\texttt{ggplot()}\) function from the \(\texttt{ggplot2}\) package. Note that after loading \(\texttt{ggplot2}\), we could have also written \(\texttt{ggplot()}\).

2.2 First Steps

Let’s use our first graph to answer a question: Do cars with big engines use more fuel than cars with small engines? You probably already have an answer, but try to make your answer precise. What does the relationship between engine size and fuel efficiency look like? Is it positive? Negative? Linear? Nonlinear?

2.2.1 The \(\texttt{mpg}\) Data Frame

You can test your answer with the \(\texttt{mpg}\) data frame found in \(\texttt{ggplot2}\) (aka \(\texttt{ggplot2::mpg}\)). Remember that a data frame is a rectangular collection of variables (in the columns) and observations (in the rows), like an Excel sheet. \(\texttt{mpg}\) contains observations collected by the US Environmental Protection Agency on 38 models of car. Run the code block below to get an idea of the content of the \(\texttt{mpg}\) data set.

# Print the content of the mpg data frame
print(ggplot2::mpg)
## # A tibble: 234 x 11
##    manufacturer model    displ  year   cyl trans   drv     cty   hwy fl    class
##    <chr>        <chr>    <dbl> <int> <int> <chr>   <chr> <int> <int> <chr> <chr>
##  1 audi         a4         1.8  1999     4 auto(l… f        18    29 p     comp…
##  2 audi         a4         1.8  1999     4 manual… f        21    29 p     comp…
##  3 audi         a4         2    2008     4 manual… f        20    31 p     comp…
##  4 audi         a4         2    2008     4 auto(a… f        21    30 p     comp…
##  5 audi         a4         2.8  1999     6 auto(l… f        16    26 p     comp…
##  6 audi         a4         2.8  1999     6 manual… f        18    26 p     comp…
##  7 audi         a4         3.1  2008     6 auto(a… f        18    27 p     comp…
##  8 audi         a4 quat…   1.8  1999     4 manual… 4        18    26 p     comp…
##  9 audi         a4 quat…   1.8  1999     4 auto(l… 4        16    25 p     comp…
## 10 audi         a4 quat…   2    2008     4 manual… 4        20    28 p     comp…
## # … with 224 more rows

Among the variables in mpg are:

  1. \(\texttt{displ}\), a car’s engine size, in litres.
  2. \(\texttt{hwy}\), a car’s fuel efficiency on the highway, in miles per gallon (mpg). A car with a low fuel efficiency consumes more fuel than a car with a high fuel efficiency when they travel the same distance.

To learn more about \(\texttt{mpg}\), open its help page by running \(\texttt{?mpg}\) or \(\texttt{help(mpg)}\) in RStudio.

?mpg

2.2.2 Creating a ggplot

To plot \(\texttt{mpg}\), run this code to put \(\texttt{displ}\) on the \(x\)-axis and \(\texttt{hwy}\) on the \(y\)-axis:

# Run this code block to display the ggplot
ggplot(mpg, aes(x=displ, y=hwy)) + geom_point()

The plot shows a negative relationship between engine size (\(\texttt{displ}\)) and fuel efficiency (\(\texttt{hwy}\)). In other words, cars with big engines use more fuel. Does this confirm or refute your hypothesis about fuel efficiency and engine size?

With \(\texttt{ggplot2}\), you begin a plot with the function \(\texttt{ggplot()}\). \(\texttt{ggplot()}\) creates a coordinate system that you can add layers to. The first argument of \(\texttt{ggplot()}\) is the dataset to use in the graph. So \(\texttt{ggplot(data = mpg)}\) creates an empty graph, but it is not very interesting so we are not going to show it here (run the code yourself if you want to see the outcome).

You complete your graph by adding one or more layers to \(\texttt{ggplot()}\). The function \(\texttt{geom_point()}\) adds a layer of points to your plot, which creates a scatterplot. \(\texttt{ggplot2}\) comes with many geom functions that each add a different type of layer to a plot. You will learn a whole bunch of them throughout this chapter.

Each geom function in ggplot2 takes a mapping argument. This defines how variables in your dataset are mapped to visual properties. The mapping argument is always paired with \(\texttt{aes()}\), and the \(x\) and \(y\) arguments of \(\texttt{aes()}\) specify which variables to map to the \(x\) and \(y\) axes. \(\texttt{ggplot2}\) looks for the mapped variables in the data argument, in this case, \(\texttt{mpg}\).

2.2.3 A graphing template

Let’s turn this code into a reusable template for making graphs with \(\texttt{ggplot2}\). To make a graph, replace the bracketed sections in the code below with a dataset, a geom function, or a collection of mappings.

## NOT RUNNABLE
#ggplot2::ggplot(data = <DATA>) + 
#  <GEOM_FUNCTION>(mapping = ggplot2::aes(<MAPPINGS>))

The rest of this chapter will show you how to complete and extend this template to make different types of graphs. We will begin with the \(\texttt{<MAPPINGS>}\) component.

2.3 Aesthetic Mappings

“The greatest value of a picture is when it forces us to notice what we never expected to see.” — John Tukey

In the plot below, one group of points (highlighted in red) seems to fall outside of the linear trend. These cars have a higher mileage than you might expect. How can you explain these cars?

Scatter plot with outliers in red

Let’s hypothesize that the cars are hybrids. One way to test this hypothesis is to look at the \(\texttt{class}\) value for each car. The \(\texttt{class}\) variable of the \(\texttt{mpg}\) dataset classifies cars into groups such as compact, midsize, and SUV. If the outlying points are hybrids, they should be classified as compact cars or, perhaps, subcompact cars (keep in mind that this data was collected before hybrid trucks and SUVs became popular).

You can add a third variable, like \(\texttt{class}\), to a two dimensional scatterplot by mapping it to an aesthetic. An aesthetic is a visual property of the objects in your plot. Aesthetics include things like the size, the shape, or the color of your points. You can display a point (like the one below) in different ways by changing the values of its aesthetic properties. Since we already use the word “value” to describe data, let’s use the word “level” to describe aesthetic properties. Here we change the levels of a point’s size, shape, and color to make the point small, triangular, or blue:

Different labels for points in graphs

You can convey information about your data by mapping the aesthetics in your plot to the variables in your dataset. For example, you can map the colors of your points to the class variable to reveal the \(\texttt{class}\) of each car. Run the code block below to see this in action.

# Demonstration of using different colors per class
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy, color = class))

(If you prefer British English, you can use \(\texttt{colour}\) instead of \(\texttt{color}\).)

To map an aesthetic to a variable, associate the name of the aesthetic to the name of the variable inside \(\texttt{aes()}\). \(\texttt{ggplot2}\) will automatically assign a unique level of the aesthetic (here a unique color) to each unique value of the variable, a process known as scaling. \(\texttt{ggplot2}\) will also add a legend that explains which levels correspond to which values.

The colors reveal that many of the unusual points are two-seater cars. These cars do not seem like hybrids, and are, in fact, sports cars! Sports cars have large engines like SUVs and pickup trucks, but small bodies like midsize and compact cars, which improves their gas mileage. In hindsight, these cars were unlikely to be hybrids since they have large engines.

In the above example, we mapped \(\texttt{class}\) to the color aesthetic, but we could have mapped \(\texttt{class}\) to the size aesthetic in the same way. In this case, the exact size of each point would reveal its \(\texttt{class}\) affiliation. We get a warning here, because mapping an unordered variable (\(\texttt{class}\)) to an ordered aesthetic (\(\texttt{size}\)) is not a good idea. Run the demonstration below to see this in action.

# Demonstration of using an unordered variable (class) to an ordered aesthetic (size)
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, size = class))
## Warning: Using size for a discrete variable is not advised.

Or we could have mapped class to the \(\texttt{alpha}\) aesthetic, which controls the transparency of the points, or to the \(\texttt{shape}\) aesthetic, which controls the shape of the points. Run the demonstration below to see this in action.

## Demonstration of using the alpha and shape aesthetics
# Top
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy, alpha = class))
## Warning: Using alpha for a discrete variable is not advised.

# Bottom
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy, shape = class))
## Warning: The shape palette can deal with a maximum of 6 discrete values because
## more than 6 becomes difficult to discriminate; you have 7. Consider
## specifying shapes manually if you must have them.
## Warning: Removed 62 rows containing missing values (geom_point).

What happened to the SUVs? \(\texttt{ggplot2}\) will only use six shapes at a time. By default, additional groups will go unplotted when you use the \(\texttt{shape}\) aesthetic.

For each aesthetic, you use \(\texttt{aes()}\) to associate the name of the aesthetic with a variable to display. The \(\texttt{aes()}\) function gathers together each of the aesthetic mappings used by a layer and passes them to the layer’s mapping argument. The syntax highlights a useful insight about \(x\) and \(y\): the \(x\) and \(y\) locations of a point are themselves aesthetics, visual properties that you can map to variables to display information about the data.

Once you map an aesthetic, \(\texttt{ggplot2}\) takes care of the rest. It selects a reasonable scale to use with the aesthetic, and it constructs a legend that explains the mapping between levels and values. For \(x\) and \(y\) aesthetics, \(\texttt{ggplot2}\) does not create a legend, but it creates an axis line with tick marks and a label. The axis line acts as a legend; it explains the mapping between locations and values.

You can also set the aesthetic properties of your geom manually. For example, we can make all of the points in our plot blue:

# Demonstration of setting an aesthetic property manually; in this case all points are set to be blue
ggplot(mpg) + 
  geom_point(aes(x = displ, y = hwy), color = "blue")

Here, the color does not convey information about a variable, but only changes the appearance of the plot. To set an aesthetic manually, set the aesthetic by name as an argument of your geom function; i.e. it goes outside of \(\texttt{aes()}\). You will need to pick a level that makes sense for that aesthetic: * The name of a color as a character string. * The size of a point in mm. * The shape of a point as a number, as shown in the figure below.

Overview of the built-in shapes that are identified by numbers

As the figure above shows, R has 25 built in shapes that are identified by numbers. There are some seeming duplicates: for example, 0, 15, and 22 are all squares. The difference comes from the interaction of the colour and fill aesthetics. The hollow shapes (0–14) have a border determined by colour; the solid shapes (15–18) are filled with colour; the filled shapes (21–24) have a border of colour and are filled with fill.

2.4 Common Problems

As you start to run R code, you are likely to run into problems. Do not worry — it happens to everyone. Even people who have been writing R code for years, make a lot of mistakes. Remember to stay patient and keep trying, and be vocal; ask your fellow students to help you when you get stuck!

Start by carefully comparing the code that you are running to the code in the book. R is extremely picky, and a misplaced character can make all the difference. Make sure that every \((\) is matched with a \()\) and every \("\) is paired with another \("\). Sometimes you will run the code and nothing happens. Check the left-hand of your console: if it is a \(+\) (when you are using RStudio), it means that R does not think you have typed a complete expression and it is waiting for you to finish it. In this case, it is usually easy to start from scratch again by pressing \(\texttt{ESCAPE}\) to abort processing the current command.

One common problem when creating \(\texttt{ggplot2}\) graphics is to put the \(+\) in the wrong place: it has to come at the end of the line, not the start. In other words, make sure you have not accidentally written code like this:

# Demonstration of a common error when working with ggplot2
#ggplot2::ggplot(data = ggplot2::mpg)
#+ ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy))

If you are still stuck, try the help. You can get help about any R function by running \(\texttt{?function_name}\) in the console, or selecting the function name and pressing \(\texttt{F1}\) in RStudio. Do not worry if the help does not seem that helpful - instead skip down to the examples and look for code that matches what you are trying to do. In fact, the latter is what you (and everyone else) is going to be doing 99% of the time.

If that does not help, carefully read the error message. Sometimes the answer will be buried there! But when you are new to R, the answer might be in the error message but you do not yet know how to understand it. Another great tool is Google: try googling the error message, as it is likely someone else has had the same problem, and has gotten help online. And again, be vocal: ask your fellow students!

2.5 Facets

One way to add additional variables is with aesthetics. Another way, particularly useful for categorical variables, is to split your plot into facets, subplots that each display one subset of the data.

To facet your plot by a single variable, use \(\texttt{facet_wrap()}\). The first argument of \(\texttt{facet_wrap()}\) should be a formula, which you create with ~ followed by a variable name (here “formula” is the name of a data structure in R, not a synonym for “equation”). The variable that you pass to \(\texttt{facet_wrap()}\) should be discrete.

# Demonstration using facets
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy)) + 
  ggplot2::facet_wrap(~ class, nrow = 2)

To facet your plot on the combination of two variables, add \(\texttt{facet_grid()}\) to your plot call. The first argument of \(\texttt{facet_grid()}\) is also a formula. This time the formula should contain two variable names separated by a ~.

# Demonstration of using the facet_grid() function
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy)) + 
  ggplot2::facet_grid(drv ~ cyl)

If you prefer to not facet in the rows or columns dimension, use a . instead of a variable name, e.g. + facet_grid(. ~ cyl). Run the demonstration below to get the idea.

# Demonstration of using the facet_grid() function
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy)) + 
  ggplot2::facet_grid(. ~ cyl)

Optional chapters

The content below are not part of the course, but it is left here if you are interested in creating more advanced plots in R.

2.6 Geometric Objects

How are these two plots similar?

Scatter plot Smoothed graph

Both plots contain the same \(x\) variable, the same \(y\) variable, and both describe the same data. But the plots are not identical. Each plot uses a different visual object to represent the data. In \(\texttt{ggplot2}\) syntax, we say that they use different geoms.

A geom is the geometrical object that a plot uses to represent data. People often describe plots by the type of geom that the plot uses. For example, bar charts use bar geoms, line charts use line geoms, boxplots use boxplot geoms, and so on. Scatterplots break the trend; they use the point geom. As we see above, you can use different geoms to plot the same data. The plot on the left uses the point geom, and the plot on the right uses the smooth geom, a smooth line fitted to the data.

To change the geom in your plot, change the geom function that you add to \(\texttt{ggplot()}\). For instance, to make the plots above, you can use this code:

# Top graph
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy))

# Bottom graph
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_smooth(mapping = ggplot2::aes(x = displ, y = hwy))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Every geom function in ggplot2 takes a \(\texttt{mapping}\) argument. However, not every aesthetic works with every geom. You could set the shape of a point, but you could not set the “shape” of a line. On the other hand, you could set the linetype of a line. \(\texttt{geom_smooth()}\) will draw a different line, with a different linetype, for each unique value of the variable that you map to linetype.

# Demonstration of using different line types for different variable values
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_smooth(mapping = ggplot2::aes(x = displ, y = hwy, linetype = drv))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Here \(\texttt{geom_smooth()}\) separates the cars into three lines based on their \(\texttt{drv}\) value, which describes a car’s drivetrain. One line describes all of the points with a \(\texttt{4}\) value, one line describes all of the points with an \(\texttt{f}\) value, and one line describes all of the points with an \(\texttt{r}\) value. Here, \(\texttt{4}\) stands for four-wheel drive, \(\texttt{f}\) for front-wheel drive, and \(\texttt{r}\) for rear-wheel drive.

If this sounds strange, we can make it more clear by overlaying the lines on top of the raw data and then coloring everything according to \(\texttt{drv}\).

Demonstration of having different lines and different colours

Notice that this plot contains two geoms in the same graph! If this makes you excited, buckle up. We will learn how to place multiple geoms in the same plot very soon.

\(\texttt{ggplot2}\) provides over 30 geoms, and extension packages provide even more (see https://www.ggplot2-exts.org for a sampling). The best way to get a comprehensive overview is the \(\texttt{ggplot2}\) cheatsheet, which you can find at https://rstudio.com/wp-content/uploads/2015/03/ggplot2-cheatsheet.pdf. To learn more about any single geom, use help: \(\texttt{?geom_smooth}\).

Many geoms, like \(\texttt{geom_smooth()}\), use a single geometric object to display multiple rows of data. For these geoms, you can set the \(\texttt{group}\) aesthetic to a categorical variable to draw multiple objects. \(\texttt{ggplot2}\) will draw a separate object for each unique value of the grouping variable. In practice, \(\texttt{ggplot2}\) will automatically group the data for these geoms whenever you map an aesthetic to a discrete variable (as in the \(\texttt{linetype}\) example). It is convenient to rely on this feature because the group aesthetic by itself does not add a legend or distinguishing features to the geoms.

# Demonstration of using geom_smooth() with group aesthetic

ggplot2::ggplot(data = ggplot2::mpg) +
  ggplot2::geom_smooth(mapping = ggplot2::aes(x = displ, y = hwy))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

ggplot2::ggplot(data = ggplot2::mpg) +
  ggplot2::geom_smooth(mapping = ggplot2::aes(x = displ, y = hwy, group = drv))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

ggplot2::ggplot(data = ggplot2::mpg) +
  ggplot2::geom_smooth(
    mapping = ggplot2::aes(x = displ, y = hwy, color = drv),
    show.legend = F
  )
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

To display multiple geoms in the same plot, add multiple geom functions to \(\texttt{ggplot()}\):

ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy)) +
  ggplot2::geom_smooth(mapping = ggplot2::aes(x = displ, y = hwy))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

This, however, introduces some duplication in our code. Imagine if you wanted to change the \(y\)-axis to display \(\texttt{cty}\) instead of \(\texttt{hwy}\). You would need to change the variable in two places, and you might forget to update one. You can avoid this type of repetition by passing a set of mappings to \(\texttt{ggplot()}\). \(\texttt{ggplot2}\) will treat these mappings as global mappings that apply to each geom in the graph. In other words, this code will produce the same plot as the previous code:

ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = displ, y = hwy)) + 
  ggplot2::geom_point() + 
  ggplot2::geom_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

If you place mappings in a geom function, \(\texttt{ggplot2}\) will treat them as local mappings for the layer. It will use these mappings to extend or overwrite the global mappings for that layer only. This makes it possible to display different aesthetics in different layers.

ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = displ, y = hwy)) + 
  ggplot2::geom_point(mapping = ggplot2::aes(color = class)) + 
  ggplot2::geom_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

You can use the same idea to specify different data for each layer. Here, our smooth line displays just a subset of the \(\texttt{mpg}\) dataset, the subcompact cars. The local data argument in \(\texttt{geom_smooth()}\) overrides the global data argument in \(\texttt{ggplot()}\) for that layer only.

ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = displ, y = hwy)) + 
  ggplot2::geom_point(mapping = ggplot2::aes(color = class)) + 
  ggplot2::geom_smooth(data = dplyr::filter(ggplot2::mpg, class == "subcompact"), se = FALSE)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

(We did and will not cover how \(\texttt{filter()}\) works: for now, just know that this command selects only the subcompact cars.)

Exercise

Exercise 1

What geom would you use to draw a line chart? A boxplot? A histogram? An area chart?

??geom
Exercise 2

Run this code in your head and predict what the output will look like. Then, run the code in R and check your predictions.

ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = displ, y = hwy, color = drv)) + 
  ggplot2::geom_point() + 
  ggplot2::geom_smooth(se = FALSE)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

What does \(\texttt{show.legend = FALSE}\) do? What happens if you remove it? Why do you think we used it earlier in the chapter?

Exercise 4

What does the \(\texttt{se}\) argument to \(\texttt{geom_smooth()}\) do?

Exercise 5

Will these two graphs look different? Why/why not?

ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = displ, y = hwy)) + 
  ggplot2::geom_point() + 
  ggplot2::geom_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

ggplot2::ggplot() + 
  ggplot2::geom_point(data = ggplot2::mpg, mapping = ggplot2::aes(x = displ, y = hwy)) + 
  ggplot2::geom_smooth(data = ggplot2::mpg, mapping = ggplot2::aes(x = displ, y = hwy))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Exercise 5

Recreate the R code necessary to generate the following graphs.

library(tidyverse)
ggplot(mpg, aes(x=displ, y=hwy)) + 
geom_point(aes(colour=drv)) + 
geom_smooth(se=FALSE)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Image 1 Image 2 Image 3 Image 4 Image 5 Image 6

2.7 Statistical Transformations

Next, let’s take a look at a bar chart. Bar charts seem simple, but they are interesting because they reveal something subtle about plots. Consider a basic bar chart, as drawn with \(\texttt{geom_bar()}\). The following chart displays the total number of diamonds in the \(\texttt{diamonds}\) dataset, grouped by \(\texttt{cut}\). The diamonds dataset comes in \(\texttt{ggplot2}\) and contains information about ~54,000 diamonds, including the \(\texttt{price}\), \(\texttt{carat}\), \(\texttt{color}\), \(\texttt{clarity}\), and \(\texttt{cut}\) of each diamond. The chart shows that more diamonds are available with high quality cuts than with low quality cuts.

ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(mapping = ggplot2::aes(x = cut))

On the \(x\)-axis, the chart displays \(\texttt{cut}\), a variable from \(\texttt{diamonds}\). On the \(y\)-axis, it displays count, but count is not a variable in \(\texttt{diamonds}\)! Where does count come from? Many graphs, like scatterplots, plot the raw values of your dataset. Other graphs, like bar charts, calculate new values to plot:

  • bar charts, histograms, and frequency polygons bin your data and then plot bin counts, the number of points that fall in each bin.

  • smoothers fit a model to your data and then plot predictions from the model.

  • boxplots compute a robust summary of the distribution and then display a specially formatted box.

The algorithm used to calculate new values for a graph is called a stat, short for statistical transformation. The figure below describes how this process works with \(\texttt{geom_bar()}\).

geom_bar() explained

You can learn which stat a geom uses by inspecting the default value for the \(\texttt{stat}\) argument. For example, \(\texttt{?geom_bar}\) shows that the default value for \(\texttt{stat}\) is “count”, which means that \(\texttt{geom_bar()}\) uses \(\texttt{stat_count()}\). \(\texttt{stat_count()}\) is documented on the same page as \(\texttt{geom_bar()}\), and if you scroll down you can find a section called “Computed variables”. That describes how it computes two new variables: \(\texttt{count}\) and \(\texttt{prop}\).

You can generally use geoms and stats interchangeably. For example, you can recreate the previous plot using \(\texttt{stat_count()}\) instead of \(\texttt{geom_bar()}\):

ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::stat_count(mapping = ggplot2::aes(x = cut))

This works because every geom has a default stat; and every stat has a default geom. This means that you can typically use geoms without worrying about the underlying statistical transformation.

Exercises

Exercise 1

What is the default geom associated with \(\texttt{stat_summary()}\)? How could you rewrite the previous plot to use that geom function instead of the stat function?

Exercise 2

What does \(\texttt{geom_col()}\) do? How is it different to \(\texttt{geom_bar()}\)?

Exercise 3

Most geoms and stats come in pairs that are almost always used in concert. Read through the documentation and make a list of all the pairs. What do they have in common?

Exercise 4

What variables does \(\texttt{stat_smooth()}\) compute? What parameters control its behaviour?

Exercise 5

What variables does \(\texttt{stat_smooth()}\) compute? What parameters control its behaviour?

# NOT RUNNABLE
ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(mapping = ggplot2::aes(x = cut, y = ..prop..))

ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(mapping = ggplot2::aes(x = cut, fill = color, y = ..prop..))

4.8 Position Adjustments

There is one more piece of magic associated with bar charts. You can colour a bar chart using either the \(\texttt{colour}\) aesthetic, or, more usefully, \(\texttt{fill}\):

## Demonstration of using colour vs. fill

# Create bar chart using the colour aesthetic
ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(mapping = ggplot2::aes(x = cut, colour = cut))

# Create a bar chart using the fill aesthetic
ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(mapping = ggplot2::aes(x = cut, fill = cut))

Note what happens if you map the \(\texttt{fill}\) aesthetic to another variable, like \(\texttt{clarity}\): the bars are automatically stacked. Each colored rectangle represents a combination of \(\texttt{cut}\) and \(\texttt{clarity}\). Run the code block below for a demonstration.

# Demonstration of using the fill aesthetic with a variable
ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(mapping = ggplot2::aes(x = cut, fill = clarity))

The stacking is performed automatically by the position adjustment specified by the \(\texttt{position}\) argument. If you do not want a stacked bar chart, you can use one of three other options: “identity”, “dodge” or “fill”.

  • \(\texttt{position = "identity"}\) will place each object exactly where it falls in the context of the graph. This is not very useful for bars, because it overlaps them. To see that overlapping we either need to make the bars slightly transparent by setting \(\texttt{alpha}\) to a small value, or completely transparent by setting \(\texttt{fill = NA}\). Run the block below for a demonstration.
ggplot2::ggplot(data = ggplot2::diamonds, mapping = ggplot2::aes(x = cut, fill = clarity)) + 
  ggplot2::geom_bar(alpha = 1/5, position = "identity")

ggplot2::ggplot(data = ggplot2::diamonds, mapping = ggplot2::aes(x = cut, colour = clarity)) + 
  ggplot2::geom_bar(fill = NA, position = "identity")

  • \(\texttt{position = "fill"}\) works like stacking, but makes each set of stacked bars the same height. This makes it easier to compare proportions across groups. Run the code block below for a demonstration.
ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(mapping = ggplot2::aes(x = cut, fill = clarity), position = "fill")

  • \(\texttt{position = "dodge"}\) places overlapping objects directly beside one another. This makes it easier to compare individual values.
ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(mapping = ggplot2::aes(x = cut, fill = clarity), position = "dodge")

There is one other type of adjustment that is not useful for bar charts, but it can be very useful for scatterplots. Recall our first scatterplot. Did you notice that the plot displays only 126 points, even though there are 234 observations in the dataset?

Scatter plot of the 126 observations

The values of \(\texttt{hwy}\) and \(\texttt{displ}\) are rounded so the points appear on a grid and many points overlap each other. This problem is known as overplotting. This arrangement makes it hard to see where the mass of the data is. Are the data points spread equally throughout the graph, or is there one special combination of \(\texttt{hwy}\) and \(\texttt{displ}\) that contains 109 values?

You can avoid this gridding by setting the position adjustment to “jitter”. \(\texttt{position = "jitter"}\) adds a small amount of random noise to each point. This spreads the points out because no two points are likely to receive the same amount of random noise.

# Demonstration of position jitter
ggplot2::ggplot(data = ggplot2::mpg) + 
  ggplot2::geom_point(mapping = ggplot2::aes(x = displ, y = hwy), position = "jitter")

Adding randomness seems like a strange way to improve your plot, but while it makes your graph less accurate at small scales, it makes your graph more revealing at large scales. Because this is such a useful operation, \(\texttt{ggplot2}\) comes with a shorthand for \(\texttt{geom_point(position = "jitter")}\): \(\texttt{geom_jitter()}\).

To learn more about a position adjustment, look up the help page associated with each adjustment: \(\texttt{?position_dodge}\), \(\texttt{?position_fill}\), \(\texttt{?position_identity}\), \(\texttt{?position_jitter}\), and \(\texttt{?position_stack}\).

Exercises

Exercise 1

What is the problem with this plot? How could you improve it?

ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = cty, y = hwy)) + 
  ggplot2::geom_point()

Exercise 2

What parameters to \(\texttt{geom_jitter()}\) control the amount of jittering?

Exercise 3

Compare and contrast \(\texttt{geom_jitter()}\) with \(\texttt{geom_count()}\).

Exercise 4

What is the default position adjustment for \(\texttt{geom_boxplot()}\)? Create a visualisation of the \(\texttt{mpg}\) dataset that demonstrates it.

4.9 Coordinate Systems

Coordinate systems are probably the most complicated part of \(\texttt{ggplot2}\). The default coordinate system is the Cartesian coordinate system where the \(x\) and \(y\) positions act independently to determine the location of each point. There are a number of other coordinate systems that are occasionally helpful. * \(\texttt{coord_flip()}\) switches the \(x\) and \(y\) axes. This is useful (for example), if you want horizontal boxplots. It is also useful for long labels: it is hard to get them to fit without overlapping on the \(x\)-axis. Runt he code block below for a demonstration.

## Demonstration of using coord_flip()
ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = class, y = hwy)) + 
  ggplot2::geom_boxplot()

ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = class, y = hwy)) + 
  ggplot2::geom_boxplot() +
  ggplot2::coord_flip()

  • \(\texttt{coord_quickmap()}\) sets the aspect ratio correctly for maps. This is very important if you are plotting spatial data with \(\texttt{ggplot2}\) (which we do not cover in this course).
bar = ggplot2::ggplot(data = ggplot2::diamonds) + 
  ggplot2::geom_bar(
    mapping = ggplot2::aes(x = cut, fill = cut), 
    show.legend = FALSE,
    width = 1
  ) + 
  ggplot2::theme(aspect.ratio = 1) +
  ggplot2::labs(x = NULL, y = NULL)

bar + ggplot2::coord_flip()

bar + ggplot2::coord_polar()

Exercises

Exercise 1

Turn a stacked bar chart into a pie chart using \(\texttt{coord_polar()}\).

Exercise 2

What does \(\texttt{labs()}\) do? Read the documentation.

Exercise 3

What is the difference between \(\texttt{coord_quickmap()}\) and \(\texttt{coord_map()}\)?

Exercise 4

What does the plot below tell you about the relationship between city and highway \(\texttt{mpg}\)? Why is \(\texttt{coord_fixed()}\) important? What does \(\texttt{geom_abline()}\) do?

ggplot2::ggplot(data = ggplot2::mpg, mapping = ggplot2::aes(x = cty, y = hwy)) +
  ggplot2::geom_point() + 
  ggplot2::geom_abline() +
  ggplot2::coord_fixed()

4.10 The Layered Grammar of Graphics

In the previous sections, you learned much more than how to make scatterplots, bar charts, and boxplots. You learned a foundation that you can use to make any type of plot with \(\texttt{ggplot2}\). To see this, let’s add position adjustments, stats, coordinate systems, and faceting to our code template:

# NOT RUNNABLE
#ggplot(data = <DATA>) + 
#  <GEOM_FUNCTION>(
#     mapping = aes(<MAPPINGS>),
#     stat = <STAT>, 
#     position = <POSITION>
#  ) +
#  <COORDINATE_FUNCTION> +
#  <FACET_FUNCTION>

Our new template takes seven parameters, the bracketed words that appear in the template. In practice, you rarely need to supply all seven parameters to make a graph because \(\texttt{ggplot2}\) will provide useful defaults for everything except the data, the mappings, and the geom function.

The seven parameters in the template compose the grammar of graphics, a formal system for building plots. The grammar of graphics is based on the insight that you can uniquely describe any plot as a combination of a dataset, a geom, a set of mappings, a stat, a position adjustment, a coordinate system, and a faceting scheme.

To see how this works, consider how you could build a basic plot from scratch: you could start with a dataset and then transform it into the information that you want to display (with a stat).

stat_count() in action

Next, you could choose a geometric object to represent each observation in the transformed data. You could then use the aesthetic properties of the geoms to represent variables in the data. You would map the values of each variable to the levels of an aesthetic.

fill and count in action

You would then select a coordinate system to place the geoms into. You would use the location of the objects (which is itself an aesthetic property) to display the values of the \(x\) and \(y\) variables. At that point, you would have a complete graph, but you could further adjust the positions of the geoms within the coordinate system (a position adjustment) or split the graph into subplots (faceting). You could also extend the plot by adding one or more additional layers, where each additional layer uses a dataset, a geom, a set of mappings, a stat, and a position adjustment.

Cartesian in action

You could use this method to build any plot that you imagine. In other words, you can use the code template that you’ve learned in this chapter to build hundreds of thousands of unique plots.